package legumes.server.usl.services;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import legumes.server.admin.tools.Misc;
import legumes.shared.domain.MailStallParserException;
import legumes.shared.domain.MailStallParserLineException;
import legumes.shared.domain.Menu;
import legumes.shared.domain.MenuItem;
import legumes.shared.domain.OnSaleProduct;
import legumes.shared.domain.Price;
import legumes.shared.domain.Product;
import legumes.shared.domain.Quantity;
import legumes.shared.domain.Receipe;
import legumes.shared.domain.Stall;
import legumes.shared.domain.Unit;

/**
 * Parser of mail sent by the merchant
 * 
 * @author cpipo
 */
public class MailStallParser {

	private String mailContent;
	private Stall stall;

	public MailStallParser(InputStream mailContent_) throws IOException {
		this(Misc.readASCII(mailContent_));
	}

	public MailStallParser(String mailContent_) {
		mailContent = mailContent_;
	}

	public void parse() throws MailStallParserException {
		stall = new Stall();
		try {
			parseSeulementSurCommande();
			parseOnSaleProducts();
			parseReceipes();
			// we do not parse menu anymore because there are too many
			// ambiguities ! We prefer let the user define it himself.
			// parseMenu();
		} catch (IOException e_) {
			throw new MailStallParserException(e_.getMessage());
		}
	}

	public Stall getStall() {
		return stall;
	}

	private void parseReceipes() throws IOException {
		try {
			BufferedReader in = new BufferedReader(
					new StringReader(mailContent));

			String line = readUntil(in, ".*RECETTES.*");

			while (!noCaseSensitiveMatches(line = nextNonEmptyLine(in),
					".*Seulement sur commande.*", ".*Hors panier.*")) {
				try {
					Receipe receipe = parseReceipe(line);
					stall.addReceipe(receipe);
				} catch (MailStallParserLineException e_) {
					stall.addImportMisunderstoodLine(e_.getLine());
				}
			}

		} catch (EOFException e_) {

		}
	}

	private Receipe parseReceipe(String line_)
			throws MailStallParserLineException {
		Pattern p = Pattern.compile("(.+)[ ]{2,}(.*)");

		Matcher m = p.matcher(line_);
		if (!m.matches()) {
			throw new MailStallParserLineException(line_);
		}

		String name = m.group(1).trim();
		String text = m.group(2).trim();

		Receipe receipe = new Receipe();
		receipe.setName(name);
		receipe.setText(text);

		return receipe;

	}

	/**
	 * Indicates if s matches at least one regexp.
	 * 
	 * @param s_
	 * @param regexp_
	 * @param otherRegexps_
	 * @return
	 */
	private static boolean noCaseSensitiveMatches(String s_, String regexp_,
			String... otherRegexps_) {
		if (s_.toUpperCase().matches(regexp_.toUpperCase())) {
			return true;
		}
		for (String regexp : otherRegexps_) {
			if (s_.toUpperCase().matches(regexp.toUpperCase())) {
				return true;
			}
		}
		return false;
	}

	private void parseSeulementSurCommande() throws IOException {
		try {
			BufferedReader in = new BufferedReader(
					new StringReader(mailContent));

			String line = readUntil(in, ".*Seulement sur commande.*");

			// Read until "Hors panier"
			while (!noCaseSensitiveMatches(line = nextNonEmptyLine(in),
					".*Hors panier.*")) {
				try {
					OnSaleProduct osp = parseOnSaleProduct(line);
					stall.addOnSaleProduct(osp);
				} catch (MailStallParserLineException e_) {
					stall.addImportMisunderstoodLine(e_.getLine());
				}
			}
		} catch (EOFException e_) {
			// mailStallParserLineExceptions.add(new
			// MailStallParserLineException(
			// "Fin de texte innatendue lors de la lecture des produits"));
		}

	}

	private void parseOnSaleProducts() throws IOException {

		try {
			BufferedReader in = new BufferedReader(
					new StringReader(mailContent));

			String line = readUntil(in, ".*Hors panier.*");

			// Skip "Hors panier"
			line = nextNonEmptyLine(in);

			// Read until "BONNE SEMAINE"
			while (!(line = nextNonEmptyLine(in)).matches("^BONNE SEMAINE.*")) {
				try {
					OnSaleProduct osp = parseOnSaleProduct(line);
					stall.addOnSaleProduct(osp);
				} catch (MailStallParserLineException e_) {
					stall.addImportMisunderstoodLine(e_.getLine());
				}
			}

		} catch (EOFException e_) {
			// mailStallParserLineExceptions.add(new
			// MailStallParserLineException(
			// "Fin de texte innatendue lors de la lecture des produits"));
		}

	}

	private void parseMenu() throws IOException {
		Menu menu = new Menu();
		try {
			BufferedReader in = new BufferedReader(
					new StringReader(mailContent));

			readUntil(in, ".*Voici la composition.*");

			String menuLine = nextNonEmptyLine(in);
			// parse menu line
			String[] itemsStr = menuLine.split("-");
			for (String itemStr : itemsStr) {
				try {
					itemStr = itemStr.trim();
					// searching the longest word
					String[] words = itemStr.split("[\\s']+");
					String longestWord = "";
					for (String word : words) {
						if (word.length() > longestWord.length()
								&& !word.equalsIgnoreCase("botte")) {
							longestWord = word;
						}
					}
					// the longest word is probably the name of the product
					String productName = longestWord;

					// see if the product name is a on sale or if it
					// is a specific menu item
					OnSaleProduct osp = null;
					String productNameBis = productName.substring(0,
							productName.length() - 1).toUpperCase();
					List<OnSaleProduct> ospCandidates = new ArrayList<OnSaleProduct>();
					for (OnSaleProduct osp2 : stall.getOnSaleProducts()) {
						if (osp2.getProduct().getName().toUpperCase().indexOf(
								productNameBis) != -1) {
							ospCandidates.add(osp2);
						}
					}
					if (ospCandidates.size() > 1) {
						StringBuilder sb = new StringBuilder();
						sb
								.append("Il y a des ambiguités pour le choix du produit \""
										+ productName + "\" dans le menu : ");
						for (OnSaleProduct o : ospCandidates) {
							sb.append(o.getProduct().getName());
							sb.append(", ");
						}
						sb.append(" ? Complétez le menu manuellement !");
						stall.addImportMessage(sb.toString());
						continue;
					}

					// find the quantity
					Pattern p = Pattern.compile("(\\d+)\\s*(\\w+).*");
					Matcher m = p.matcher(itemStr);
					Quantity quantity = null;
					String specialUnit = null;
					if (m.matches()) {
						float q;
						q = Float.parseFloat(m.group(1));
						String unitStr = m.group(2);
						Unit unit = Unit.fromDisplayName(unitStr);
						if (unit == null) {
							if (unitStr.equals(productName)) {
								// Example : 1 concombre
								unit = Unit.UNITY;
							} else {
								unit = Unit.UNITY;
								// it may be "botte" or anything else...
								specialUnit = unitStr;
							}
						}
						quantity = new Quantity(q, unit);
					} else {
						stall.addImportMisunderstoodLine(itemStr);
					}

					// name of menu item
					String menuItemName = specialUnit == null ? productName
							: productName + " (" + specialUnit + ")";

					MenuItem menuItem = new MenuItem();
					if (osp == null) {
//						menuItem.setMenuProduct(menuItemName);
					} else {
						menuItem.setOnSaleProduct(osp);
					}
					menuItem.setQuantity(quantity);
					menu.addMenuWholeItem(menuItem);
				} catch (Exception e_) {
					stall.addImportMisunderstoodLine(itemStr);
				}

			}

			// parse menu prices
			readUntil(in, ".*Prix.*");
			String wholePriceLine = nextNonEmptyLine(in);
			String halfPriceLine = nextNonEmptyLine(in);
			Pattern p = Pattern.compile(".*Panier:\\s*(\\d*\\.\\d*).*");
			Matcher m = p.matcher(wholePriceLine);
			if (m.matches()) {
				try {
					float wholePrice = Float.parseFloat(m.group(1));
					menu.setWholePrice(wholePrice);
				} catch (NumberFormatException e_) {
					stall.addImportMessage("Prix du panier introuvable.");
				}
			} else {
				stall.addImportMessage("Prix du panier introuvable.");
			}
			m = p.matcher(halfPriceLine);
			if (m.matches()) {
				try {
					float halfPrice = Float.parseFloat(m.group(1));
					menu.setHalfPrice(halfPrice);
				} catch (NumberFormatException e_) {
					stall.addImportMessage("Prix du 1/2 panier introuvable.");
				}

			} else {
				stall.addImportMessage("Prix du 1/2 panier introuvable.");
			}

		} catch (EOFException e_) {
			stall
					.addImportMessage("Fin du mail innatendue... Vérifiez le menu");
		}

		stall.setMenu(menu);
	}

	private OnSaleProduct parseOnSaleProduct(String line_)
			throws MailStallParserLineException {
		Pattern p = Pattern
				.compile("(\\D+)[ ]{1,}(\\d+\\.\\d+)\\s*E?/?\\s*(\\d*)(\\w*)");

		Matcher m = p.matcher(line_);
		if (!m.matches()) {
			throw new MailStallParserLineException(line_);
		}

		String productNameStr = m.group(1).trim();
		String priceStr = m.group(2).trim();
		String quantityStr = m.group(3).trim();
		String unitStr = m.group(4).trim();

		Quantity quantity = new Quantity();

		// Unit and quantity
		quantity.setValue(quantityStr.equals("") ? 1F : Float
				.parseFloat(quantityStr));

		Unit unit = Unit.fromDisplayName(unitStr);
		if (unit == null) {
			if (unitStr.length() > 0) {
				productNameStr += " (" + unitStr + ")";
			}
			quantity.setUnitAsEnum(Unit.UNITY);
		} else {
			quantity.setUnitAsEnum(unit);
		}

		OnSaleProduct osp = new OnSaleProduct();
		Product product = new Product();
		product.setName(productNameStr);
		osp.setProduct(product);
		Price price = new Price();
		osp.setPrice(price);
		price.setAmount(Float.parseFloat(priceStr));
		price.setQuantity(quantity);

		return osp;
	}

	private String nextNonEmptyLine(BufferedReader in_) throws IOException {
		String line;

		while ((line = in_.readLine()) != null
				&& (line = line.trim()).length() == 0)
			;

		if (line == null) {
			throw new EOFException();
		}
		return line;
	}

	/**
	 * Reads until finding a line matching the given regexp.
	 * 
	 * @param in_
	 * @param regexp
	 * @return the matching line if found else null
	 * @throws IOException
	 */
	private String readUntil(BufferedReader in_, String regexp_)
			throws IOException {
		String line;

		while (!noCaseSensitiveMatches(line = nextNonEmptyLine(in_), regexp_))
			;

		return line;

	}

}
